﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;

namespace Client
{
    public partial class MainForm : Form
    {
        public const byte CONN_FAIL = 0;
        public const byte CONN_STOP = 1;

        public const byte FIRST_FRAME = 0;
        public const byte FRAME_MODIFY = 1;
        public const byte TRANSMISSION_PAUSED = 2;

        private delegate void SetImageCallback(Bitmap bmp);
        private delegate void UpdateFrameCallback(Bitmap bmp, int x, int y);

        private volatile bool userClosed;
        public delegate void newMessageCallback (string message);
        public delegate void setClipboardCallback(DataObject data);
        public delegate void closeCallback(byte reason);
        private String password,nickname;
        private UInt16 port;
        private IPAddress IPa;
        private ChatDialog chat;
        private TcpClient Conn = null;
        private ClientConfig conf;

        private Thread chatThread = null;
        private Thread clipThread = null;
        private Thread videoThread = null;
        private TcpClient clipClient;
        private TcpClient videoClient;

        private Object bitmapMonitor = new Object();
        private Graphics gfx;
        private volatile bool paused;
        private newMessageCallback showMessage;
        public MainForm()
        {
            InitializeComponent();
            userClosed = false;
            conf = new ClientConfig();
            DialogResult ris = conf.ShowDialog(this);
            switch (ris) 
            {
                case DialogResult.OK:
                    password = conf.getPassword;
                    port = conf.getPort;
                    nickname = conf.getNick;
                    IPa = conf.getIp;
                    Conn = new TcpClient();
                    chat = new ChatDialog(Conn);
                    showMessage = new newMessageCallback(chat.newChatMessage);
                    chat.Show();
                    chatThread = new Thread(new ThreadStart(this.DoConnect));
                    chatThread.Start();
                    break;
                default:
                    Environment.Exit(0);
                    break; 
            }
        }
        /// <summary>
        /// thread relativo alla parte messaggistica
        /// </summary>
        public void DoConnect() 
        { 
            closeCallback closeCallback = new closeCallback(closeProgram);
            String mess;
            try
            {
                Conn.Connect(IPa, port);
            }
            catch (SocketException)
            {
                Conn.Close();
                this.BeginInvoke(closeCallback, CONN_FAIL);
                return;
            }
            StreamWriter write = new StreamWriter(Conn.GetStream());
            StreamReader read = new StreamReader(Conn.GetStream());
            BinaryReader bReader = new BinaryReader(Conn.GetStream());
            try
            {
                write.WriteLine(conf.getNick);
                write.WriteLine(conf.getPassword);
                write.Flush();
                mess = read.ReadLine();
                if (mess.Equals("409 - Conflict"))
                {
                    MessageBox.Show("Username conflict","Error");
                    Conn.Close();
                }
                else if (mess.Equals("403 - Forbidden"))
                {
                    MessageBox.Show("Password error", "Error");
                    Conn.Close();
                }
                else if (mess.Equals("200 - Clipboard"))
                {
                    int sessId = bReader.ReadInt32();
                    int clipPort = bReader.ReadInt32();
                    int videoPort = bReader.ReadInt32();

                    clipClient = new TcpClient();
                    clipClient.Connect(IPa, clipPort);
                    BinaryWriter binWriter = new BinaryWriter(clipClient.GetStream());
                    binWriter.Write(sessId);
                    chat.setConnection(clipClient);
                    //LETTURA CONFERMA
                    mess = read.ReadLine();
                    if (mess.Equals("201 - Video"))
                    {
                        videoClient = new TcpClient();
                        videoClient.Connect(IPa, videoPort);
                        binWriter = new BinaryWriter(videoClient.GetStream());
                        binWriter.Write(sessId);

                        mess = read.ReadLine();
                        if (mess.Equals("202 - Completed"))
                        {
                            String ricev;
                            chat.Invoke(showMessage, Environment.NewLine + "--SERVER INFORMATION: Connection completed--" + Environment.NewLine);
                            clipThread = new Thread(new ThreadStart(ClipMain));
                            clipThread.Start();

                            videoThread = new Thread(new ThreadStart(VideoMain));
                            videoThread.Start();

                            while ((ricev = read.ReadLine()) != null)
                            {
                                chat.Invoke(showMessage, ricev);
                            }
                        }
                        else
                        {
                            videoClient.Close();
                            clipClient.Close();
                        }
                    }
                    else
                        clipClient.Close();
                }
            }
            catch (SocketException)
            {
            }
            catch (IOException)
            {
            }
            finally
            {
                Conn.Close();
                if (!userClosed && !this.IsDisposed)
                    this.BeginInvoke(closeCallback, CONN_STOP);  //Non uso invoke, ma la chiamata asincrona begininvoke, perchè nel metodo chiudo il form e attendo il thread, avrei deadlock
            }
        }

        /// <summary>
        /// thread relativo alla gestione della clipboard
        /// </summary>
        public void ClipMain() 
        {
            BinaryReader bReader = new BinaryReader(clipClient.GetStream());
            setClipboardCallback setClip=new setClipboardCallback (chat.setClipboard);
            String type;
            try
            {
                while (true)
                {
                    type = bReader.ReadString();
                    int dim = bReader.ReadInt32();
                    byte[] buffer = new byte[dim];
                    buffer = bReader.ReadBytes(dim);
                    if ((!type.Equals(DataFormats.Text)) && (!type.Equals(DataFormats.Bitmap)))
                    {
                        using (FileStream fs = new FileStream(type, FileMode.OpenOrCreate, FileAccess.Write))
                        {
                            fs.Write(buffer, 0, buffer.Length);
                            fs.Close();
                        }
                        String percorso = Path.GetFullPath(type);
                        DataObject data = new DataObject();
                        data.SetData(DataFormats.FileDrop, true, new String[] { percorso });
                        chat.Invoke(setClip, data);
                    }
                    else
                    {
                        using (MemoryStream ms = new MemoryStream())
                        {
                            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binForm = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                            ms.Write(buffer, 0, buffer.Length);
                            ms.Seek(0, SeekOrigin.Begin);
                            DataObject data = new DataObject();
                            data.SetData(type, binForm.Deserialize(ms));
                            chat.Invoke(setClip, data);
                        }
                    }
                    chat.Invoke(showMessage, Environment.NewLine + "--NEW CLIPBOARD CONTENT AVAILABLE: " + type + "--");
                }
            }
            catch (SocketException)
            {
            }
            catch (IOException)
            {
            }
            finally
            {
                clipClient.Close();
                videoClient.Close();
                Conn.Close();
            }
        }
        /// <summary>
        /// thread relativo alla parte di gestione del video
        /// </summary>
        public void VideoMain()
        {
            SetImageCallback setImageCallback = new SetImageCallback(SetImage);
            BinaryReader bread = new BinaryReader(videoClient.GetStream());
            byte code;
            try
            {
                while (true)
                {
                    code = bread.ReadByte();
                    if (code == FIRST_FRAME)
                    {
                        paused = false;
                        //int w = bread.ReadInt32();
                        //int h = bread.ReadInt32();
                        //Bitmap newBmp = new Bitmap(w, h);
                        int len = bread.ReadInt32();

                        byte[] bytes = bread.ReadBytes(len);
                        using (MemoryStream ms = new MemoryStream(bytes))
                        {
                            Bitmap bmp = new Bitmap(ms);
                            this.Invoke(setImageCallback, bmp);
                            //pictureBox1.Image = bmp;
                        }
                        if (paused)
                            paused = false;
                    }
                    else if (code == FRAME_MODIFY)
                    {
                        int x = bread.ReadInt32();
                        int y = bread.ReadInt32();
                        int len = bread.ReadInt32();

                        byte[] bytes = bread.ReadBytes(len);
                        //object[] parameters = { bytes, x, y };
                        if (!paused)  //Se sono paused devo ricevere first frame
                            UpdateFrame(bytes, x, y);
                    }
                    else if (code == TRANSMISSION_PAUSED)
                    {
                        paused = true;
                        Bitmap bmp = (Bitmap)Properties.Resources.ResourceManager.GetObject("sospesa");
                        this.Invoke(setImageCallback, bmp);
                        //ResourceManager rm = new ResourceManager("Resources.resx", Assembly.GetExecutingAssembly());
                        //Bitmap bmp = (Bitmap)rm.GetObject("sospesa.jpg");
                        //this.Invoke(setImageCallback, bmp);
                    }
                }
            }
            catch (SocketException)
            {
            }
            catch (IOException)
            {
            }
            finally
            {
                clipClient.Close();
                videoClient.Close();
                Conn.Close();
            }
        }

        void closeProgram(byte reason)
        {
            chat.ReleaseClipboard();
            chat.Close();
            chat.Dispose();
            switch (reason)
            {
                case CONN_FAIL:
                    MessageBox.Show("Connection failed", "Error");
                    break;
                case CONN_STOP:
                    MessageBox.Show("Connection stopped", "Error");
                    break;
                default:
                    MessageBox.Show("Unknown error", "Error");
                    break;
            }
            this.Close();
        }
        
        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            userClosed = true;
            if (chat != null && !chat.IsDisposed)
            {
                chat.ReleaseClipboard();
                chat.Close();
                chat.Dispose();
            }
            if (chatThread != null)
            {
                Conn.Close();
                chatThread.Join();
            }
            if (videoThread != null)
            {
                videoClient.Close();
                videoThread.Join();
            }
            if (clipThread != null)
            {
                clipClient.Close();
                clipThread.Join();
            }
        }

        private void UpdateFrame(byte[] bytes, int x, int y)
        {
            using (MemoryStream ms = new MemoryStream(bytes))
            {
                Bitmap bmp = new Bitmap(ms);
                Monitor.Enter(bitmapMonitor);
                if (pictureBox1.Image != null)
                {
                    gfx.DrawImage(bmp, x, y);
                }
                Monitor.Exit(bitmapMonitor);
                bmp.Dispose();
            }
        }

        private void SetImage(Bitmap bmp)
        {
            Monitor.Enter(bitmapMonitor);
            if (pictureBox1.Image != null)
            {
                pictureBox1.Image.Dispose();
                gfx.Dispose();//
            }
            pictureBox1.Image = bmp;
            gfx = Graphics.FromImage(pictureBox1.Image);
            Monitor.Exit(bitmapMonitor);
        }

        private void pictureBox1_Resize(object sender, EventArgs e)
        {
            this.ClientSize = new Size(pictureBox1.Width + 2 * pictureBox1.Left, pictureBox1.Height + 2 * pictureBox1.Top);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            Monitor.Enter(bitmapMonitor);
            pictureBox1.Invalidate();
            Monitor.Exit(bitmapMonitor);
        }
    }
}